<?php
/**
 * 增删改查，一般用于管理后台
 */
declare(strict_types=1);

namespace App\Helper;

use App\Exception\BusinessException;
use App\Model\Model;
use Carbon\Carbon;
use Hyperf\DbConnection\Db;
use Hyperf\ModelCache\Manager;
use Hyperf\Utils\ApplicationContext;
use Throwable;

trait CurdTrait
{
    /**
     * 获取列表
     * @param array|null $get
     * @return mixed
     */
    public function list(?array $get = null)
    {
        is_null($get) && $get = $this->request->query();
        if (method_exists($this, 'fixListQuery')) {
            $get = $this->fixListQuery($get);
        }
        $page = max(1, intval($get['page'] ?? 1));
        $limit = intval($get['limit'] ?? 0);
        $order = explode(',', strval($get['order'] ?? ''));
        $orders = [];
        foreach ($order as $item) {
            $item = explode(' ', trim($item));
            $column = $item[0] ?? '';
            if (empty($column)) {
                continue;
            }
            $ascOrDesc = strtolower($item[1] ?? 'asc');
            in_array($ascOrDesc, ['asc', 'desc'], true) || $ascOrDesc = 'asc';
            $orders[$column] = $ascOrDesc;
        }
        unset($get['page'], $get['limit'], $get['order']);
        is_array($get) || $get = [];
        try {
            [$table, $connection] = $this->getModelInfo();
            $query = Db::connection($connection)->table($table);
            foreach ($orders as $column => $ascOrDesc) {
                $query->orderBy($column, $ascOrDesc);
            }
            foreach ($get as $column => $value) {
                if (empty($column)) {
                    continue;
                }
                $value = trim(strval($value));
                $valueLen = strlen($value);
                $columnReal = substr($column, 10);
                switch (substr($column, 0, 10)) {
                    case 'like_with_':
                        if ($columnReal && $valueLen) {
                            $query->where($columnReal, 'like', '%' . $value . '%');
                        }
                        break;
                    case 'less_than_':
                        if ($columnReal && $valueLen) {
                            $query->where($columnReal, '<=', $value);
                        }
                        break;
                    case 'more_than_':
                        if ($columnReal && $valueLen) {
                            $query->where($columnReal, '>=', $value);
                        }
                        break;
                    case 'null_with_':
                        if ($columnReal && $valueLen) {
                            $query->whereNull($columnReal);
                        }
                        break;
                    case 'not__null_':
                        if ($columnReal && $valueLen) {
                            $query->whereNotNull($columnReal);
                        }
                        break;
                    default:
                        if ($valueLen) {
                            $query->where($column, $value);
                        }
                }
            }
            if ($limit) {
                $count = $query->count();
                $data = $query->forPage($page, $limit)->get();
            } else {
                $data = $query->get();
                $count = count($data);
            }
            if (method_exists($this, 'fixListData')) {
                $data = $this->fixListData($data->all());
            }
            return $this->response->json(self::genSuccessData($data, $count));
        } catch (Throwable $throwable) {
            return $this->response->json(self::genErrorData($throwable->getMessage(), $throwable->getCode()));
        }
    }

    /**
     * 按配置获取模型的：表名、连接名、主键名、时间戳、软删除字段
     * @return array
     * @throws BusinessException
     */
    private function getModelInfo(): array
    {
        $modelClass = ($this->model ?? '');
        if (!$modelClass) {
            throw new BusinessException(0, '控制器未指定模型！');
        }
        /* @var Model $model */
        $model = ApplicationContext::getContainer()->get($modelClass);
        $timestampColumns = false;
        if ($model->usesTimestamps()) {
            $timestampColumns = [
                'created_at' => $model->getCreatedAtColumn(),
                'updated_at' => $model->getUpdatedAtColumn(),
            ];
        }
        $deletedAtColumn = false;
        if (method_exists($model, 'trashed') && method_exists($model, 'getDeletedAtColumn')) {
            $deletedAtColumn = $model->getDeletedAtColumn();
        }
        return [
            $model->getTable(),
            $model->getConnectionName(),
            $model->getKeyName(),
            $timestampColumns,
            $deletedAtColumn,
        ];
    }

    /**
     * 获取详情
     * @param int|null $id
     * @return mixed
     */
    public function info(?int $id = null)
    {
        is_null($id) && $id = intval($this->request->query('id', 0));
        if ($id <= 0) {
            return $this->response->json(self::genErrorData('查询ID错误或未指定！'));
        }
        try {
            [$table, $connection, $idName] = $this->getModelInfo();
            $data = Db::connection($connection)->table($table)->where($idName, $id)->first();
            if (method_exists($this, 'fixInfoData')) {
                $data = $this->fixInfoData($data);
            }
            return $this->response->json(self::genSuccessData($data));
        } catch (Throwable $throwable) {
            return $this->response->json(self::genErrorData($throwable->getMessage(), $throwable->getCode()));
        }
    }

    /**
     * 创建一条
     * @param array|null $post
     * @return mixed
     */
    public function create(?array $post = null)
    {
        is_null($post) && $post = $this->request->post();
        try {
            if (method_exists($this, 'fixPostData')) {
                $post = $this->fixPostData($post);
            }
            [$table, $connection, $idName, $timestampColumns] = $this->getModelInfo();
            unset($post[$idName]);
            if (!$post) {
                return $this->response->json(self::genErrorData('无效的POST数据！'));
            }
            $createdAt = ($timestampColumns ? ($timestampColumns['created_at'] ?: '') : '');
            if ($createdAt && !key_exists($createdAt, $post)) {
                $post[$createdAt] = Carbon::now();
            }
            $newId = Db::connection($connection)->table($table)->insertGetId($post);
            $returnData = ['new_id' => $newId];
            if (method_exists($this, 'afterCreated')) {
                $returnData = $this->afterCreated($newId, $post);
            }
            return $this->response->json(self::genSuccessData($returnData));
        } catch (Throwable $throwable) {
            return $this->response->json(self::genErrorData($throwable->getMessage(), $throwable->getCode()));
        }
    }

    /**
     * 恢复一条
     * @param int|null $id
     * @return mixed
     */
    public function resume(?int $id = null)
    {
        is_null($id) && $id = intval($this->request->post('id', 0));
        if ($id <= 0) {
            return $this->response->json(self::genErrorData('恢复ID错误或未指定！'));
        }
        return $this->resumeMulti(strval($id));
    }

    /**
     * 恢复批量
     * @param string|null $ids
     * @return mixed
     */
    public function resumeMulti(?string $ids = null)
    {
        is_null($ids) && $ids = $this->request->post('ids', '');
        $idsArr = self::getIdsAsArray($ids);
        if (!$idsArr) {
            return $this->response->json(self::genErrorData('恢复ID错误或未指定！'));
        }
        try {
            [$table, $connection, $idName, $timestampColumns, $deletedAtColumn] = $this->getModelInfo();
            unset($timestampColumns);
            if (!$deletedAtColumn) {
                return $this->response->json(self::genErrorData('未使用软删除，不能恢复！'));
            }
            $rowsCount = Db::connection($connection)->table($table)->whereIn($idName, $idsArr)->update([$deletedAtColumn => null]);
            $rowsCount && $this->deleteModelCache($idsArr);
            $returnData = ['rows_count' => $rowsCount];
            if (method_exists($this, 'afterResumed')) {
                $returnData = $this->afterResumed($rowsCount, $idsArr);
            }
            return $this->response->json(self::genSuccessData($returnData));
        } catch (Throwable $throwable) {
            return $this->response->json(self::genErrorData($throwable->getMessage(), $throwable->getCode()));
        }
    }

    /**
     * 将串转换成数组
     * @param string $ids
     * @return array
     */
    private static function getIdsAsArray(string $ids): array
    {
        $ids = explode(',', $ids);
        $idsArr = [];
        foreach ($ids as $id) {
            $id = intval($id);
            $id > 0 && array_push($idsArr, $id);
        }
        return array_unique($idsArr);
    }

    /**
     * 删除模型缓存
     * @param array|null $ids
     */
    private function deleteModelCache(?array $ids): void
    {
        if (!$ids) {
            return;
        }
        if (method_exists($this, 'beforeDeleteModelCache')) {
            $this->beforeDeleteModelCache($ids);
        }
        ApplicationContext::getContainer()->get(Manager::class)->destroy($ids, $this->model);
    }

    /**
     * 删除一条
     * @param int|null $id
     * @param int|null $force
     * @return mixed
     */
    public function delete(?int $id = null, ?int $force = null)
    {
        is_null($id) && $id = intval($this->request->post('id', 0));
        if ($id <= 0) {
            return $this->response->json(self::genErrorData('删除ID错误或未指定！'));
        }
        is_null($force) && $force = intval($this->request->post('force', 0)); // 软删除情况下，做硬删除
        return $this->deleteMulti(strval($id), $force);
    }

    /**
     * 删除批量
     * @param string|null $ids
     * @param int|null $force
     * @return mixed
     */
    public function deleteMulti(?string $ids = null, ?int $force = null)
    {
        is_null($ids) && $ids = $this->request->post('ids', '');
        $idsArr = self::getIdsAsArray($ids);
        if (!$idsArr) {
            return $this->response->json(self::genErrorData('删除ID错误或未指定！'));
        }
        is_null($force) && $force = intval($this->request->post('force', 0)); // 软删除情况下，做硬删除
        try {
            [$table, $connection, $idName, $timestampColumns, $deletedAtColumn] = $this->getModelInfo();
            unset($timestampColumns);
            $query = Db::connection($connection)->table($table)->whereIn($idName, $idsArr);
            $soft = false;
            if ($force > 0 || !$deletedAtColumn) {
                $rowsCount = $query->delete();
            } else {
                $soft = true;
                $rowsCount = $query->update([$deletedAtColumn => Carbon::now()]);
            }
            $rowsCount && $this->deleteModelCache($idsArr);
            $returnData = ['rows_count' => $rowsCount];
            if (method_exists($this, 'afterDeleted')) {
                $returnData = $this->afterDeleted($rowsCount, $idsArr, $soft);
            }
            return $this->response->json(self::genSuccessData($returnData));
        } catch (Throwable $throwable) {
            return $this->response->json(self::genErrorData($throwable->getMessage(), $throwable->getCode()));
        }
    }

    /**
     * 更新一条
     * @param array|null $post
     * @return mixed
     */
    public function update(?array $post = null)
    {
        is_null($post) && $post = $this->request->post();
        try {
            if (method_exists($this, 'fixPostData')) {
                $post = $this->fixPostData($post);
            }
            [$table, $connection, $idName, $timestampColumns] = $this->getModelInfo();
            $id = intval($post[$idName] ?? 0);
            if ($id <= 0) {
                return $this->response->json(self::genErrorData('修改ID错误或未指定！'));
            }
            unset($post[$idName]);
            if (!$post) {
                return $this->response->json(self::genErrorData('无效的POST数据！'));
            }
            $updatedAt = ($timestampColumns ? ($timestampColumns['updated_at'] ?: '') : '');
            if ($updatedAt && !key_exists($updatedAt, $post)) {
                $post[$updatedAt] = Carbon::now();
            }
            $rowsCount = Db::connection($connection)->table($table)->where($idName, $id)->update($post);
            $rowsCount && $this->deleteModelCache([$id]);
            $returnData = ['rows_count' => $rowsCount];
            if (method_exists($this, 'afterUpdated')) {
                $returnData = $this->afterUpdated($rowsCount, $id, $post);
            }
            return $this->response->json(self::genSuccessData($returnData));
        } catch (Throwable $throwable) {
            return $this->response->json(self::genErrorData($throwable->getMessage(), $throwable->getCode()));
        }
    }
}
